<?php
Yii::import('application.modules.Core.models.Category');
class NestedBehavior extends CBehavior
{
    public $className = null;
    public $attributeParentId = 'parent_id';
    public $attributeOrdering = 'ordering';
    public $attributeStatus = 'is_active';
    public $criteria;
    
    public function getNestedCacheKey()
    {
        return __CLASS__.'::'.__FUNCTION__.'.'.$this->className.'.'.$this->attributeParentId.'.'.$this->attributeOrdering;
    }
    
    public function sort($params)
    {
        Yii::trace(__CLASS__.'::'.__FUNCTION__.':'.CVarDumper::dumpAsString($params));
        $ids = $this->getOwner()->getParam($params, 'items', array());
        if (count($ids)) {
            foreach ($ids as $id => $parentId) {
                $orders = array();
                if ($parentId == 'root') {
                    $orders = array_keys($ids, $parentId);
                    $parentId = 0;
                }
                else {
                    $parentId = (int) $parentId;
                    $orders = array_keys($ids, $parentId);
                }
                $order = 0;
                if (count($orders))
                    $order = (int) array_search($id, $orders);
                $order++;
                Category::model($this->className)->updateByPk($id, array(
                    $this->attributeParentId=>$parentId,
                    $this->attributeOrdering=>$order,
                ));
                
                //flag update cache
//                $this->dataChanged();
            }
        }
        return $this->getOwner()->result;
    }
    
    public function status($params)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        $value = $this->getOwner()->getParam($params, 'value', 0);
        $model = Category::model()->findByPk($id);
        if (is_object($model)) {
            $model->is_active = $value;
            if (!$model->save(false, array($this->attributeStatus)))
                $this->getOwner()->result->fail(ERROR_HANDLING_DB, Yii::t('CoreModule.nested','Error while saving submitted data into database.'));
            
            //flag update cache
            $this->dataChanged();
        } else
            $this->getOwner()->result->fail(ERROR_INVALID_DATA, Yii::t('CoreModule.nested', 'Submitted data is missing or invalid.'));
        return $this->getOwner()->result;
    }
    
    /**
    * flag update cache
    * 
    */
    public function dataChanged()
    {
        $result = FSM::_run('Admin.Cache.rebuildCacheByName', array('name' => $this->getNestedCacheKey()));
        if ($result->hasErrors())
            return false;
        return true;
    }
    
    /**
    * get dependency object
    * 
    * @return CCacheDependency|null
    */
    public function getCacheDependency()
    {
        $result = FSM::_run('Admin.Cache.getCacheDependency', array(
            'name' => $this->getNestedCacheKey(),
            'description' => 'Force rebuild cache form '.$this->className,
        ));
        if (!$result->hasErrors())
            return $result->dependency;
        return null;
    }
    
    /**
    * get duration x second cache
    * 
    * @return int
    */
    public function getExpire()
    {
        $result = FSM::_run('Admin.Cache.getExpire', array('name' => $this->getNestedCacheKey()));
        if (!$result->hasErrors())
            return is_int($result->duration) ? $result->duration : -1;
        return 0;
    }

    /**
    * Build a category tree
    * 
    * @param mixed $params
    */
    public function buildTree($params = null)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        $level = $this->getOwner()->getParam($params, 'level', 0);
        $status = $this->getOwner()->getParam($params, 'status', null);//null => select all, 1=> only select public category
        $attributes = $this->getOwner()->getParam($params, 'attributes', array());
        
        if(false && (($cache=Yii::app()->cache)!==null) && $this->getExpire())
        {
            $key=__CLASS__.'::'.__FUNCTION__.$this->className.$this->attributeParentId.$this->attributeOrdering.'.'.$id;
            if(($data=$cache->get($key))!==false) {
                $tree = unserialize($data);
                $this->getOwner()->result->processed('tree', $tree);
                return $this->getOwner()->result;
            }
        }
        
        //Get top level categories
        $criteria=new CDbCriteria;
        if (is_object($this->criteria) && $this->criteria instanceof CDbCriteria)
            $criteria->mergeWith($this->criteria);
        $criteria->compare($this->attributeParentId, $id);
        if (isset($status))
            $criteria->compare($this->attributeStatus, $status);
        $criteria->order = $this->attributeOrdering;
        $models = Category::model($this->className)->findAll($criteria);
        
        $tree = $this->subTree($models, $status, $level, $attributes);
        if(false && isset($cache))
        {
            $cache->set($key,serialize($tree), $this->getExpire(), $this->getCacheDependency());
        }
        
        $this->getOwner()->result->processed('tree', $tree);
        return $this->getOwner()->result;
    }
    
    /**
    * Create subtree, internally use by Category service
    * 
    * @param Category $models
    * @param int $level
    * 
    * @return array
    */
    protected function subTree($models, $status=null, $level = 0, $attributes=array())
    {
        static $data;
        if (!isset($data))
            $data = array();
        foreach ($models as $index => $model) {
            if (isset($status) && $model->{$this->attributeStatus} != $status) continue;
            $children = $model->children;
            $data[$model->id] = array(
                'level' => $level,
                'parent' => $model->{$this->attributeParentId},
                'children' => array(),
            );
            if (is_array($attributes) && count($attributes))
            {
                foreach ($attributes as $attribute)
                    if ($model->hasAttribute($attribute))
                        $data[$model->id][$attribute] = $model->$attribute;
            }
            if (is_array($children) && count($children)) {
                $data[$model->id]['children'] = CHtml::listData($children, 'id', 'id');
                $this->subTree($children, $status, $level+1, $attributes);
            }
        }
        return $data;
    }
    
    /**
    * Build a sub category tree give the root ID of the sub-tree
    * 
    * @param array $params
    *   - int id: Id of category which is the root of sub-tree
    */
    public function buildSubTree($params)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        $status = $this->getOwner()->getParam($params, 'status', null);
        
        if ($id == 0)
            return $this->getOwner()->result->processed('tree', null, 'Invalid '.$this->className.' ID');
            
        return $this->buildTree(array('id'=>$id, 'status'=>$status));
    }
    
    /**
    * Get path of category Ids from root to the current category
    * 
    * @param mixed $params
    *   - int id
    * @return FServiceModel
    */
    public function path($params)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        
        if ($id == 0)
            return $this->getOwner()->result->fail(ERROR_INVALID_DATA, 'Invalid '.$this->className.' ID');

        if($this->getExpire() && (($cache=Yii::app()->cache)!==null))
        {
            $key=__CLASS__.'::'.__FUNCTION__.$this->className.$this->attributeParentId.$this->attributeOrdering.'.'.$id;
            if(($data=$cache->get($key))!==false) {
                $path = unserialize($data);
                $this->getOwner()->result->processed('path', $path);
                return $this->getOwner()->result;
            }
        }
            
        $criteria=new CDbCriteria;
        $criteria->compare('id', $id);
        $model = Category::model($this->className)->find($criteria);
        
        $path = array($id);
        if (is_object($model)) {
            $parent = $model->parent;
            while (is_object($parent)) {
                array_push($path, $parent->id);
                $parent = $parent->parent;
            }
        }
        
        if(isset($cache))
        {
            $cache->set($key,serialize($path), $this->getExpire(), $this->getCacheDependency());
        }
        
        $this->getOwner()->result->processed('path', $path);
        return $this->getOwner()->result;
    }

    /**
    * Find categories which are childrend of a category given its Id
    * Required param:
    *   - int id: Id of parent category
    * Optional param:
    *   - bool includeStat: Whether to include statistics (total child categories and total articles)
    *   - bool skipDescription: Whether to return description data. Use this to speed up AJAX request
    *
    * @param mixed $params
    * @return FServiceModel
    */
    public function findByParentId($params)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        $withStat = $this->getOwner()->getParam($params, 'includeStat', 0);
        $skipDescription = $this->getOwner()->getParam($params, 'skipDescription', 0);
        
        if ($id == 0)
            return $this->getOwner()->result->fail(ERROR_INVALID_DATA, 'Invalid '.$this->className.' ID');
            
        $criteria=new CDbCriteria;
        $criteria->compare($this->attributeParentId, $id);
        $criteria->order = $this->attributeOrdering;
        
        if ($skipDescription) {
            $attributes = Category::model($this->className)->getAttributes();
            if (array_key_exists('description', $attributes))
                unset($attributes['description']);
            $attributes = array_keys($attributes);
            $criteria->select = $attributes;
        }
        
        if ($withStat && Category::model($this->className)->hasRelation('countChildren'))
            $criteria->with = 'countChildren';
        
        $models = Category::model($this->className)->findAll($criteria);
        
        $this->getOwner()->result->processed('models', $models);
        return $this->getOwner()->result;
    }
    
    /**
    * Get Id of the current category and all of its descendant Ids
    * 
    * @param mixed $params
    *   - int id
    * @return array ids: list of IDs
    */
    public function getIdAndChildren($params)
    {
        $id = $this->getOwner()->getParam($params, 'id', 0);
        $status = $this->getOwner()->getParam($params, 'status', null);
        
        if ($id == 0)
            return $this->getOwner()->result->fail(ERROR_INVALID_DATA, 'Invalid '.$this->className.' ID');
        
        $ids = array($id);
        
        $data = $this->buildSubTree(array('id'=>$id, 'status'=>$status))->tree;
        if (is_array($data) && count($data))
            $ids = CMap::mergeArray($ids, array_keys($data));
            
        $this->getOwner()->result->processed('ids', $ids);
        return $this->getOwner()->result;
    }
}